// WebServer.cpp: implementation of the CWebServer class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "AUSlotsTester.h"
#include "WebServer.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CWebServer::CWebServer()
{
}

CWebServer::~CWebServer()
{
}

bool CWebServer::AddWebFile( const CWebFile* pFile )
{
	m_mapWebFiles.SetAt( pFile->GetFilePath(), pFile );

	return true;
}

bool CWebServer::AddWebFileAlias( const CWebFile* pFile, LPCTSTR szAlias )
{
	m_mapWebFiles.SetAt( szAlias, pFile );

	return true;
}

bool CWebServer::RemoveWebFile( const CWebFile* pFile )
{
	POSITION pos = m_mapWebFiles.GetStartPosition();

	CString strKey;
	const CWebFile* p;

	while ( pos )
	{
		m_mapWebFiles.GetNextAssoc( pos, strKey, p );

		if ( p == pFile )
		{
			m_mapWebFiles.RemoveKey( strKey );
		}
	}

	return true;
}

bool CWebServer::Initialize( UINT nPort /*= 0*/ )
{
	// Use the passed in port.  If it is 0 then we will automatically
	// be assigned an unused port.
	if ( !Create( nPort ) )
	{
		return false;
	}

	// Get the real port, in case they passed in 0.
	CString strAddr;
	
	if ( !GetSockName( strAddr, m_nPort ) )
	{
		return false;
	}

	// Listen for requests to come in.
	if ( !Listen() )
	{
		return false;
	}

	return true;
}

void CWebServer::OnAccept( int /*nErrorCode*/ )
{
	// Declare a socket to accept the new connection.
	CSocket connection;

	// If we are able to accept the connection...
	if ( Accept( connection ) )
	{
		// then process the HTTP request.
		ProcessRequest( connection );
	}
}

void CWebServer::ProcessRequest( CSocket& request ) const
{
	// We need a receive buffer and some variables to help track its current status.
	char buffer[1024];
	char* pCurrentPosition = buffer;
	int nRemainingSpace = sizeof(buffer);

	// Here are some token strings that we will use when parsing the HTTP request.
	char* szWhiteSpace = " \t";
	char* szNewLine = "\r\n";

	// Zero out the buffer so that we can use it like a null terminated string,
	// even if we don't have the full command yet.
	memset( buffer, 0, sizeof(buffer ));

	// Read the request into the buffer.
	for ( int nRead = request.Receive( pCurrentPosition, nRemainingSpace );
		  nRead > 0;
		  nRead = request.Receive( pCurrentPosition, nRemainingSpace ) )
	{
		// Keep up with the current status of the buffer.
		nRemainingSpace -= nRead;
		pCurrentPosition += nRead;

		// See if we got the first line yet.
		if ( strstr( buffer, szNewLine ) )
		{
			// Find the first token on the line, which is the command.
			char* pContext;
			char* p = strtok_s( buffer, szWhiteSpace, &pContext );

			// If it is the get command...
			if ( _stricmp( p, "GET" ) == 0 )
			{
				// then get the next token, which should be the file name.
				p = strtok_s( NULL, szWhiteSpace, &pContext );

				// Send the requested file to the client.
				GetFile( request, p );
			}

			// We only care about the first line.  If it is a GET command, then great- we are done.
			// If not, we don't support it so we are still done.
			break;
		}

		// Avoid buffer overruns.
		if ( nRemainingSpace == 0 )
		{
			// If we get a command with a first line that is bigger than our buffer then just
			// ignore it.  Any valid command that we support should be much smaller than our
			// buffer.
			break;
		}

		// If this ASSERT fires then we have a buffer overrun problem some how.
		ASSERT( nRemainingSpace > 0 );
	}

	// We are done with the request so close it.
	request.Close();
}

void CWebServer::GetFile( CSocket& request, LPCSTR szFileName ) const
{
	CString strFileName = szFileName;

	const CWebFile* pWebFile = NULL;

	if ( m_mapWebFiles.Lookup( szFileName, pWebFile ) )
	{
		LPCSTR szContentType = NULL;
		void* pContents = NULL;
		UINT nContentSize = 0;

		if ( pWebFile->GetFileContents( &szContentType, &pContents, &nContentSize ) )
		{
			WriteFileContents( request, szContentType, pContents, nContentSize );
		}
		else
		{
			WriteResponseStatus( request, rsInternalServerError );
		}
	}
	else
	{
		WriteResponseStatus( request, rsFileNotFound );
	}
}

UINT CWebServer::GetPort() const
{
	return m_nPort;
}

void CWebServer::WriteResponseStatus( CSocket& request, ResponseStatus eStatus )
{
	switch ( eStatus )
	{
		case rsOK:
		{
			WriteResponseStatus( request, "200 OK" );
		}
		break;

		case rsFileNotFound:
		{
			WriteResponseStatus( request, "404 Not Found" );
		}
		break;

		case rsBadRequest:
		{
			WriteResponseStatus( request, "400 Bad Request" );
		}
		break;

		case rsInternalServerError:
		{
			WriteResponseStatus( request, "500 Internal Server Error" );
		}
		break;

		default:
		{
			ASSERT( false );
		}
		break;
	}
}

void CWebServer::WriteResponseStatus( CSocket& request, LPCSTR szStatus )
{
	char* p = "HTTP/1.0 ";

	request.Send( p, strlen( p ) );
	
	request.Send( szStatus, strlen( szStatus ) );

	WriteCRLF( request );
}

void CWebServer::WriteResponseHeader( CSocket& request, LPCSTR szContentType, UINT nContentSize /*= 0*/ )
{
	char* p = "Content-Type: ";

	request.Send( p, strlen( p ) );

	request.Send( szContentType, strlen( szContentType ) );

	WriteCRLF( request );

	if ( nContentSize )
	{
		char buffer[ _CVTBUFSIZE ];

		_ultoa_s( nContentSize, buffer, sizeof( buffer ), 10 );

		p = "Content-Length: ";

		request.Send( p, strlen( p ) );

		request.Send( buffer, strlen( buffer ) );

		WriteCRLF( request );
	}

	WriteCRLF( request );
}

void CWebServer::WriteFileContents( CSocket& request, LPCSTR szContentType, void* pContents, UINT nContentSize )
{
	WriteResponseStatus( request, rsOK );
	WriteResponseHeader( request, szContentType, nContentSize );

	request.Send( pContents, nContentSize );
}
